分类
联系方式
  1. 新浪微博
  2. E-mail

BackTrader SMA 金叉策略

MA(Moving Average)指移动平均线。根据不同加权类型内部又细分为多种。本文中研究的是简单移动平均线(Simple Moving Average)。

本文只用于数学、编程研究,不提供交易指导。

代码

下面代码中 TestStrategy 策略类是通用的。

import backtrader as bt

from newstock.data.mongo.mongo_data_manager import MongoDataManager
from newstock.date.stock_date import StockDate
from newstock.market.Exchange import SZSEExchange
from newstock.market.symbol import Symbol
import pandas as pd


class TestStrategy(bt.Strategy):
    params = (
        ("short", 5),
        ("long", 10),
        ("printlog", False),
    )

    def log(self, txt, dt=None, doprint=False):
        """Logging function for this strategy"""
        if self.params.printlog or doprint:
            dt = dt or self.datas[0].datetime.date(0)
            print("%s, %s" % (dt.isoformat(), txt))

    def __init__(self):
        # Keep a reference to the "close" line in the data[0] dataseries
        self.dataclose = self.datas[0].close

        # To keep track of pending orders
        self.order = None
        self.buyprice = None
        self.buycomm = None

        sma_s = bt.ind.SMA(period=self.p.short)  # type: ignore
        sma_l = bt.ind.SMA(period=self.p.long)  # type: ignore

        self.crossover = bt.ind.CrossOver(sma_s, sma_l)  # crossover signal

    def notify_order(self, order):
        if order.status in [order.Submitted, order.Accepted]:
            # Buy/Sell order submitted/accepted to/by broker - Nothing to do
            return

        # 交易完成
        # Attention: broker could reject order if not enough cash
        if order.status in [order.Completed]:
            if order.isbuy():
                self.log(
                    "BUY EXECUTED, Price: %.2f, Cost: %.2f, Comm %.2f"
                    % (order.executed.price, order.executed.value, order.executed.comm)
                )

                self.buyprice = order.executed.price  # 买入价格
                self.buycomm = order.executed.comm  # 买入手续费
            elif order.issell():
                self.log(
                    "SELL EXECUTED, Price: %.2f, Cost: %.2f, Comm %.2f"
                    % (order.executed.price, order.executed.value, order.executed.comm)
                )

            self.bar_executed = len(self)  # 买入日期

        elif order.status in [order.Canceled, order.Margin, order.Rejected]:
            self.log("Order Canceled/Margin/Rejected")

        # Write down: no pending order
        self.order = None

    def notify_trade(self, trade):
        if not trade.isclosed:
            return

        self.log("OPERATION PROFIT, GROSS %.2f, NET %.2f" % (trade.pnl, trade.pnlcomm))

    def next(self):
        # Simply log the closing price of the series from the reference
        self.log("Close, %.2f" % self.dataclose[0])

        # Check if an order is pending ... if yes, we cannot send a 2nd one
        if self.order:
            return

        # Check if we are in the market
        if not self.position:

            # Not yet ... we MIGHT BUY if ...
            if self.crossover > 0:
                # current close less than previous close

                # BUY, BUY, BUY!!! (with default parameters)
                self.log("BUY CREATE, %.2f" % self.dataclose[0])

                # Keep track of the created order to avoid a 2nd order
                self.order = self.buy()

        else:

            # Already in the market ... we might sell
            if self.crossover < 0:
                # SELL, SELL, SELL!!! (with all possible default parameters)
                self.log("SELL CREATE, %.2f" % self.dataclose[0])

                # Keep track of the created order to avoid a 2nd order
                self.order = self.sell()

    def stop(self):
        self.log("Ending Value %.2f" % (self.broker.getvalue()), doprint=True)


if __name__ == "__main__":
    cerebro = bt.Cerebro()

    print("Starting Portfolio Value: %.2f" % cerebro.broker.getvalue())

    mongoManager = MongoDataManager()

    df = mongoManager.getStockPeriodFromDB(
        Symbol(SZSEExchange, "000001"),
        StockDate.today().previousDays(300),
        StockDate.today(),
    )
    df["date"] = pd.to_datetime(df["trade_date"], format="%Y%m%d")
    data = bt.feeds.PandasData(dataname=df, datetime="date")  # type: ignore
    df.dropna()

    # 0.1% ... divide by 100 to remove the %
    cerebro.broker.setcommission(commission=0.001)
    # Python 3.10 修复 module 'collections' has no attribute 'Iterable' 开始
    import collections

    collections.Iterable = collections.abc.Iterable
    # Python 3.10 修复 module 'collections' has no attribute 'Iterable' 完成
    # 策略参数优化
    # cerebro.optstrategy(TestStrategy, maperiod=range(10, 31))
    # 策略运行
    cerebro.addstrategy(TestStrategy)
    cerebro.adddata(data)
    # Add a FixedSize sizer according to the stake
    cerebro.addsizer(bt.sizers.FixedSize, stake=10)
    cerebro.run()
    cerebro.plot(style="bar", volume=False)

    print("Final Portfolio Value: %.2f" % cerebro.broker.getvalue())

效果

以 sz000001 最近 300 交易日数据为例。

快线5 慢线10

可以看到,最终没有赔钱,但也没有挣钱。

从共交易了 7 次,3 次是成功的,4 次是亏损的,盈亏幅度都挺大。

快线5 慢线15

交易次数变少,对于盈利没有影响。

快线10 慢线30

把均线放慢试一下。

均线放慢后,收益下降(下降了一点点)。看到交易频率进一步下降,但全是失败的。

感觉 SMA 更加适合于作为一个短线指标。

网络资源

杰明说指标 | 均线金叉死叉真有效?深度复盘数据带你一探究竟!

backtrader学习之一-经典sma金叉策略回测